/* lzfmodule.c */

#include "Python.h"
#include "lzf.h"

#ifdef WITH_THREAD
#include "pythread.h"
#endif

PyDoc_STRVAR(compress__doc__,
             "compress(string) -- Returned compressed string.\n"
             );

static PyObject *
PyLZF_compress(PyObject *self, PyObject *args)
{
  PyObject *ReturnVal = NULL;
  unsigned char *input, *outptr, *output = NULL;
  unsigned int in_len, out_len, shift;

  if (!PyArg_ParseTuple(args, "s#:compress", &input, &in_len))
    return NULL;

  if (in_len == 0){
    ReturnVal = PyString_FromString("");
  } else if (in_len <= 11) {
    ReturnVal = PyString_FromFormat("%c%s", '\0', (char *)input);
  } else {
    output = (unsigned char*)malloc(in_len+1);
    if (output == NULL) {
      PyErr_SetString(PyExc_MemoryError, "Can't allocate memory to compress data");
      return NULL;
    }
    outptr = output;
    if (in_len <= 0x7f) {
      *outptr++ = in_len;
    } else if (in_len <= 0x7ff) {
      *outptr++ = (( in_len >>  6)         | 0xc0);
      *outptr++ = (( in_len        & 0x3f) | 0x80);
    } else if (in_len <= 0xffff) {
      *outptr++ = (( in_len >> 12)         | 0xe0);
      *outptr++ = (((in_len >>  6) & 0x3f) | 0x80);
      *outptr++ = (( in_len        & 0x3f) | 0x80);
    } else if (in_len <= 0x1fffff) {
      *outptr++ = (( in_len >> 18)         | 0xf0);
      *outptr++ = (((in_len >> 12) & 0x3f) | 0x80);
      *outptr++ = (((in_len >>  6) & 0x3f) | 0x80);
      *outptr++ = (( in_len        & 0x3f) | 0x80);
    } else if (in_len <= 0x3ffffff) {
      *outptr++ = (( in_len >> 24)         | 0xf8);
      *outptr++ = (((in_len >> 18) & 0x3f) | 0x80);
      *outptr++ = (((in_len >> 12) & 0x3f) | 0x80);
      *outptr++ = (((in_len >>  6) & 0x3f) | 0x80);
      *outptr++ = (( in_len        & 0x3f) | 0x80);
    } else if (in_len <= 0x7fffffff) {
      *outptr++ = (( in_len >> 30)         | 0xfc);
      *outptr++ = (((in_len >> 24) & 0x3f) | 0x80);
      *outptr++ = (((in_len >> 18) & 0x3f) | 0x80);
      *outptr++ = (((in_len >> 12) & 0x3f) | 0x80);
      *outptr++ = (((in_len >>  6) & 0x3f) | 0x80);
      *outptr++ = (( in_len        & 0x3f) | 0x80);
    } else {
      PyErr_Format(PyExc_MemoryError, "Can't compress more than %ld bytes", 0x7fffffffL);
      goto error;
    }
    shift = (unsigned int)(outptr - output);
    Py_BEGIN_ALLOW_THREADS;
    out_len = lzf_compress ((const void *const) input, (unsigned int) in_len,
                            (void *)outptr, in_len+1-shift);
    Py_END_ALLOW_THREADS;
    
    if (out_len == 0) {
      PyErr_SetString(PyExc_MemoryError,
                      "Out of memory while compressing data");
      goto error;
    }
    ReturnVal = PyString_FromStringAndSize((char *)output, out_len+shift);
  }
  if (ReturnVal == NULL){
    PyErr_SetString(PyExc_MemoryError, "Can't allocate string for the result");
  }
  
 error:
  //if (output)
  free(output);

  return ReturnVal;
}

PyDoc_STRVAR(decompress__doc__,
               "decompress(string) -- Return decompressed string.\n"
               );

  static PyObject *
    PyLZF_decompress(PyObject *self, PyObject *args)
  {
    PyObject *ReturnVal = NULL;
    unsigned char *input, *inptr, *output = NULL;
    unsigned int in_len, out_len;

    if (!PyArg_ParseTuple(args, "s#:decompress", &input, &in_len))
      return NULL;
    inptr = input;

    if (in_len == 0){
      ReturnVal = PyString_FromString("");
    } else if (*input) {
      if (!(*input & 0x80) && in_len >= 1) {
        in_len -= 1;
        out_len =                 *input++ & 0xff;
      } else if (!(*input & 0x20) && in_len >= 2) {
        in_len -= 2;
        out_len =                 *input++ & 0x1f;
        out_len = (out_len << 6) | (*input++ & 0x3f);
      } else if (!(*input & 0x10) && in_len >= 3) {
        in_len -= 3;
        out_len =                 *input++ & 0x0f;
        out_len = (out_len << 6) | (*input++ & 0x3f);
        out_len = (out_len << 6) | (*input++ & 0x3f);
      } else if (!(*input & 0x08) && in_len >= 4) {
        in_len -= 4;
        out_len =                 *input++ & 0x07;
        out_len = (out_len << 6) | (*input++ & 0x3f);
        out_len = (out_len << 6) | (*input++ & 0x3f);
        out_len = (out_len << 6) | (*input++ & 0x3f);
      } else if (!(*input & 0x04) && in_len >= 5) {
        in_len -= 5;
        out_len =                 *input++ & 0x03;
        out_len = (out_len << 6) | (*input++ & 0x3f);
        out_len = (out_len << 6) | (*input++ & 0x3f);
        out_len = (out_len << 6) | (*input++ & 0x3f);
        out_len = (out_len << 6) | (*input++ & 0x3f);
      } else if (!(*input & 0x02) && in_len >= 6) {
        in_len -= 6;
        out_len =                 *input++ & 0x01;
        out_len = (out_len << 6) | (*input++ & 0x3f);
        out_len = (out_len << 6) | (*input++ & 0x3f);
        out_len = (out_len << 6) | (*input++ & 0x3f);
        out_len = (out_len << 6) | (*input++ & 0x3f);
        out_len = (out_len << 6) | (*input++ & 0x3f);
      } else {
        PyErr_SetString(PyExc_MemoryError, "compressed data corrupted (invalid length)");
        return NULL;
      }
      if (!out_len) {
        PyErr_SetString(PyExc_MemoryError, "compressed data corrupted (invalid length)");
        return NULL;
      }
      output = (unsigned char*)malloc(out_len);
      if (output == NULL) {
        PyErr_SetString(PyExc_MemoryError, "Can't allocate memory to compress data");
        return NULL;
      }

      Py_BEGIN_ALLOW_THREADS;
      out_len = lzf_decompress ((const void *const) input, (unsigned int) in_len,
                                (void *)output, out_len);
      Py_END_ALLOW_THREADS;

      if (out_len == 0) {
        PyErr_SetFromErrno(PyExc_RuntimeError);
        goto error;
      }
      ReturnVal = PyString_FromStringAndSize((char *)output, out_len);
    } else { // *input == 0
      ReturnVal = PyString_FromStringAndSize((char *)++input, --in_len);
    }

  error:
    free(output);
    return ReturnVal;
    }

  static PyMethodDef lzf_methods[] =
    {
      {"compress", (PyCFunction)PyLZF_compress,  METH_VARARGS, compress__doc__},
      {"decompress", (PyCFunction)PyLZF_decompress, METH_VARARGS, decompress__doc__},
      {NULL, NULL}
    };


  PyDoc_STRVAR(lzf_module_documentation,
               "The functions in this module allow compression and decompression using the\n"
               "liblzf library.\n"
               "compress(string) -- Compress string.\n"
               "decompress(string) -- Decompresses a compressed string.\n"
               );

  PyMODINIT_FUNC
    init_lzf(void)
  {
    PyObject *m, *ver;
    //Py_TYPE(&Comptype) = &PyType_Type;
    //Py_TYPE(&Decomptype) = &PyType_Type;
    m = Py_InitModule4("_lzf", lzf_methods,
                       lzf_module_documentation,
                       (PyObject*)NULL,PYTHON_API_VERSION);
    if (m == NULL)
      return;

    PyModule_AddIntConstant(m, "LZF_", 3);// FIXME:
    ver = Py_BuildValue("i", LZF_VERSION);
    // ver = PyString_FromString(LZF_VERSION);
    if (ver != NULL)
      PyModule_AddObject(m, "LZF_VERSION", ver);

    PyModule_AddStringConstant(m, "__version__", "1.0");

  }
